/**
 * <pre>
 * @(#)ScrollBarUI.java 1.00 2009-10-29
 * 
 * Copyright (c) 1998, 2009 Regular Individual. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Contributor(s):
 *     Haixu Huang
 * </pre>
 **/
package cn.sh.huang.chart.ext.ui;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.Timer;

import cn.sh.huang.chart.ext.enumtype.ScrollBarType;
import cn.sh.huang.chart.view.container.ScrollView;

/**
 * <pre>
 * ScrollBarUI ...
 * 
 * @version 1.0
 * </pre>
 */
abstract public class ScrollBarUI
{
    private final static int SCROLL_SPEED_THROTTLE = 100; // Delay on continuous step in.
    private final static int INITIAL_SCROLL_DELAY = 500; // Delay on start step in.

    private final Image hbar = getHbarTexture(), vbar = getVbarTexture();
    private final int[] idxz = getTextureIdxz();
    private final int[] dstz;
    /**
     * <code>
     * Texture for rendering scroll bar:
     * 
     *     left arrow 0, left side of slideway 1, extension of slideway 2, right side of slideway 3,
     *     right arrow 4, left side of slider 5, extension of slider 6, knot 7, right side of slider 8
     * 
     * |0|1| 2 |5|6|7|6|8| 2 |3|4|9
     * [<]{=====[   #   ]=====}[>]
     * </code>
     */
    private final int barUnit = getBarUnit(); // thickness of scroll bar

    private final ScrollView view;
    private final ScrollBarView vbarView, hbarView;

    private Timer scrollTimer;
    private int _all, _wnd, _pos, _step, _delta, _thumb;
    private float _scale;
    private ScrollBarType _barType;
    private boolean paged = false, paused = false, dragged = false;

    private final ActionListener scrollListener = new ActionListener() {

        public void actionPerformed(ActionEvent e)
        {
            doScroll();
        }
    };

    protected ScrollBarUI(ScrollView view)
    {
        this.view = view;
        // view.addMouseListener(mouseAdapter);
        // view.addMouseMotionListener(mouseMotionAdapter);

        dstz = new int[idxz.length - 1]; // 9
        for (int i = 0; i < dstz.length; i++) {
            dstz[i] = idxz[i + 1] - idxz[i];
        }

        vbarView = new ScrollBarView(ScrollBarType.VERTICAL, this);
        hbarView = new ScrollBarView(ScrollBarType.HORIZONTAL, this);

        scrollTimer = new Timer(SCROLL_SPEED_THROTTLE, scrollListener);
        scrollTimer.setInitialDelay(INITIAL_SCROLL_DELAY); // default InitialDelay?
    }

    public static final int SCROLL_UNIT = 10;

    private void doScroll()
    {
        if (!paused) {
            // TODO
            _pos += _step;
            if (_pos < 0) {
                _pos = 0;
                scrollTimer.stop();
            } else if (_pos + _wnd > _all) {
                _pos = _all - _wnd;
                scrollTimer.stop();
            } else if (paged) {
                int thumbLen = Math.round(_scale * _wnd);
                int thumbPos = Math.round(_scale * _pos);
                int tl = dstz[0] + dstz[1] + thumbPos - dstz[7] / 2, tr = tl + thumbLen + dstz[7];
                if (_thumb > tl - dstz[5] && _thumb < tr + dstz[8]) {
                    scrollTimer.stop();
                }
            }
            view.setScrollPos(_barType, _pos);
        }
    }

    public void onScroll(ScrollBarType barType, int all, int wnd, int pos, int u, int v)
    {
        // Check if mouse pressed on the slider
        _scale = (float) (u - dstz[0] - dstz[1] - dstz[3] - dstz[4]) / all;
        int thumbLen = Math.round(_scale * wnd);
        int thumbPos = Math.round(_scale * pos);
        int tl = dstz[0] + dstz[1] + thumbPos - dstz[7] / 2, tr = tl + thumbLen + dstz[7];
        if (v > tl - dstz[5] && v < tr + dstz[8]) {
            dragged = true; // scrolling by drag the slider
            _delta = thumbPos - v;
            _thumb = v;
            _barType = barType;
        } else {
            paged = (v > dstz[0] && v < u - dstz[4]);
            if (paged) {
                _thumb = v;
            }
            int unit = paged ? wnd : SCROLL_UNIT;
            onScroll(barType, all, wnd, pos, (v < tl) ? -unit : unit);
        }
    }

    public void onScroll(ScrollBarType barType, int all, int wnd, int pos, int step)
    {
        _barType = barType;
        _step = step;
        _all = all;
        _wnd = wnd;
        _pos = pos;
        scrollTimer.start();
        doScroll();
    }

    public void offScroll(ScrollBarType barType)
    {
        if (scrollTimer.isRunning()) {
            scrollTimer.stop();
        }
        paused = false;
        dragged = false;
        _barType = null;
        _pos = -1;
    }

    public void thumbScroll(ScrollBarType barType, int all, int wnd, int u, int v, int w)
    {
        if (!barType.equals(_barType)) {
            return;
        }
        if (scrollTimer.isRunning()) {
            if (paged) {
                paused = w < 0 || w > barUnit || v < dstz[0] || v > u - dstz[4];
            }
            paused = w < 0 || w > barUnit || v < 0 || v > u || (v > dstz[0] && v < u - dstz[4]);
            return;
        }
        if (dragged) {
            _scale = (float) (u - dstz[0] - dstz[1] - dstz[3] - dstz[4]) / all;
            int thumbPos = v + _delta;
            int pos = Math.round(thumbPos / _scale);
            pos = Math.max(0, pos);
            pos = Math.min(all - wnd, pos);
            if (_pos != pos) {
                _pos = pos;
                view.setScrollPos(barType, pos);
            }
        }
    }

    public ScrollBarView getVBarView()
    {
        return vbarView;
    }

    public ScrollBarView getHBarView()
    {
        return hbarView;
    }

    void renderHBar(Graphics g, Rectangle rect, int all, int wnd, int pos)
    {
        int tl = rect.x, tr = tl + rect.width, tt = rect.y, tb = tt + rect.height;

        // Left arrow
        g.drawImage(hbar, tl, tt, tl + dstz[0], tb, idxz[0], 0, idxz[1], barUnit, null);
        tl += dstz[0];
        // Left slide
        g.drawImage(hbar, tl, tt, tl + dstz[1], tb, idxz[1], 0, idxz[2], barUnit, null);
        tl += dstz[1];
        // Right arrow
        g.drawImage(hbar, tr - dstz[4], tt, tr, tb, idxz[4], 0, idxz[5], barUnit, null);
        tr -= dstz[4];
        // Right slide
        g.drawImage(hbar, tr - dstz[3], tt, tr, tb, idxz[3], 0, idxz[4], barUnit, null);
        tr -= dstz[3];
        // Scale slide
        g.drawImage(hbar, tl, tt, tr, tb, idxz[2], 0, idxz[3], barUnit, null);

        float scale = (float) (tr - tl) / all;
        int thumbLen = Math.round(scale * wnd);
        int thumbPos = Math.round(scale * pos);

        /**
         * <code>
         * |0|1| 2 |5|6|7|6|8| 2 |3|4|
         * [<]{=====[   #   ]=====}[>]
         * </code>
         */

        tl = tl + thumbPos;
        tr = tl + thumbLen;

        // Left Thumb
        tl -= dstz[7] / 2;
        g.drawImage(hbar, tl - dstz[5], tt, tl, tb, idxz[5], 0, idxz[6], barUnit, null);
        // Right thumb
        tr += dstz[7] - dstz[7] / 2;
        g.drawImage(hbar, tr, tt, tr + dstz[8], tb, idxz[8], 0, idxz[9], barUnit, null);
        // Scale thumb
        g.drawImage(hbar, tl, tt, tr, tb, idxz[6], 0, idxz[7], barUnit, null);
        // Thumb button
        tl = (tl + tr - dstz[7]) / 2;
        g.drawImage(hbar, tl, tt, tl + dstz[7], tb, idxz[7], 0, idxz[8], barUnit, null);
    }

    void renderVBar(Graphics g, Rectangle rect, int all, int wnd, int pos)
    {
        int tl = rect.x, tr = tl + rect.width, tt = rect.y, tb = tt + rect.height;

        // Top arrow
        g.drawImage(vbar, tl, tt, tr, tt + dstz[0], 0, idxz[0], barUnit, idxz[1], null);
        tt += dstz[0];
        // Left slide
        g.drawImage(vbar, tl, tt, tr, tt + dstz[1], 0, idxz[1], barUnit, idxz[2], null);
        tt += dstz[1];
        // Right arrow
        g.drawImage(vbar, tl, tb - dstz[4], tr, tb, 0, idxz[4], barUnit, idxz[5], null);
        tb -= dstz[4];
        // Right slide
        g.drawImage(vbar, tl, tb - dstz[3], tr, tb, 0, idxz[3], barUnit, idxz[4], null);
        tb -= dstz[3];
        // Scale slide
        g.drawImage(vbar, tl, tt, tr, tb, 0, idxz[2], barUnit, idxz[3], null);

        float scale = (float) (tb - tt) / all;
        int thumbLen = Math.round(scale * wnd);
        int thumbPos = Math.round(scale * pos);

        /**
         * <code>
         * |0|1| 2 |5|6|7|6|8| 2 |3|4|
         * [<]{=====[   #   ]=====}[>]
         * </code>
         */

        tt = tt + thumbPos;
        tb = tt + thumbLen;

        // Left Thumb
        tt -= dstz[7] / 2;
        g.drawImage(vbar, tl, tt - dstz[5], tr, tt, 0, idxz[5], barUnit, idxz[6], null);
        // Right thumb
        tb += dstz[7] - dstz[7] / 2;
        g.drawImage(vbar, tl, tb, tr, tb + dstz[8], 0, idxz[8], barUnit, idxz[9], null);
        // Scale thumb
        g.drawImage(vbar, tl, tt, tr, tb, 0, idxz[6], barUnit, idxz[7], null);
        // Thumb button
        tt = (tt + tb - dstz[7]) / 2;
        g.drawImage(vbar, tl, tt, tr, tt + dstz[7], 0, idxz[7], barUnit, idxz[8], null);
    }

    abstract public int getBarUnit();

    abstract protected int[] getTextureIdxz();

    abstract protected Image getHbarTexture();

    abstract protected Image getVbarTexture();
}
